﻿using System;
using System.Configuration;
using System.IO;

namespace Viyi.Util.Config
{
    /// <summary>
    /// 封装<see cref="System.Configuration.Configuration"/>，
    /// 提供对配置项的便捷操作方法。
    /// </summary>
    public class AppConfig : ReadOnlyableConfig
    {
        /// <summary>
        /// 根据指定的EXE文件，打开对应的配置文件。
        /// </summary>
        /// <param name="exePath">EXE文件路径</param>
        /// <param name="isReadOnly">指示该配置是否为只读，默认为<c>false</c></param>
        /// <returns>成功打开配置，返回<c>AppConfig</c>对象；否则返回<c>null</c>。</returns>
        /// <remarks>如果<c>isReadOnly</c>被指定为<c>true</c>，
        /// 则调用所有具有写入功能的方法时都会抛出异常。</remarks>
        public static AppConfig OpenExeConfiguration(string exePath, bool isReadOnly = false)
        {
            try
            {
                Configuration config = ConfigurationManager.OpenExeConfiguration(exePath);
                return new AppConfig(config, isReadOnly);
            }
            catch (ConfigurationErrorsException)
            {
                return null;
            }
        }

        /// <summary>
        /// 打开指定名称的配置文件。
        /// </summary>
        /// <param name="configFilePath">配置文件路径</param>
        /// <param name="isReadOnly">指示该配置是否为只读，默认为<c>false</c></param>
        /// <returns>成功打开配置，返回<c>AppConfig</c>对象；否则返回<c>null</c>。</returns>
        /// <remarks>如果<c>isReadOnly</c>被指定为<c>true</c>，
        /// 则调用所有具有写入功能的方法时都会抛出异常。</remarks>
        public static AppConfig OpenConfiguration(string configFilePath, bool isReadOnly = false)
        {
            if (isReadOnly && !File.Exists(configFilePath))
            {
                return null;
            }

            ExeConfigurationFileMap map = new ExeConfigurationFileMap
            {
                ExeConfigFilename = configFilePath
            };

            try
            {
                Configuration config = ConfigurationManager
                    .OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
                return new AppConfig(config, isReadOnly);
            }
            catch (ConfigurationErrorsException)
            {
                return null;
            }
        }

        /// <summary>
        /// 获取载入的配置对象。
        /// </summary>
        public Configuration Configuration { get; private set; }

        /// <summary>
        /// 设置/获取是否在改变了配置值之后立即保存到配置文件中
        /// </summary>
        public bool IsSaveImmediately { get; set; }

        private AppConfig(Configuration configuration, bool isReadOnly)
        {
            Configuration = configuration;
            IsReadOnly = isReadOnly;
        }

        /// <summary>
        /// 获取配置在<c>connectionStrings</c>段中指定名称的连接字符串。
        /// </summary>
        /// <param name="name">配置名称</param>
        /// <returns>配置值</returns>
        public string GetConnectionString(string name)
        {
            ConnectionStringSettings settings = Configuration
                .ConnectionStrings.ConnectionStrings[name];
            return settings == null ? null : settings.ConnectionString;
        }

        /// <summary>
        /// 在<c>connectionStrings</c>段中设置连接字符串。
        /// </summary>
        /// <param name="name">配置名称</param>
        /// <param name="value">配置值</param>
        /// <exception cref="InvalidOperationException">如果创建当前<c>AppConfig</c>
        /// 对象时<c>isReadOnly</c>为true，则会引发该异常。</exception>
        public void SetConnectionString(string name, string value)
        {
            ThrowIfReadOnly();

            ConnectionStringSettings settings = Configuration
                .ConnectionStrings.ConnectionStrings[name];
            if (settings == null)
            {
                settings = new ConnectionStringSettings(name, value);
                Configuration.ConnectionStrings.ConnectionStrings.Add(settings);
            }
            else
            {
                settings.ConnectionString = value;
            }

            OnUpdate();
        }

        /// <summary>
        /// 获取配置在<c>appSettings</c>段中指定配置键对应的配置值。
        /// </summary>
        /// <param name="key">配置键</param>
        /// <returns>配置值</returns>
        public string GetAppSetting(string key)
        {
            KeyValueConfigurationElement setting = Configuration.AppSettings.Settings[key];
            return setting == null ? null : setting.Value;
        }

        /// <summary>
        /// 在<c>appSettings</c>段中写入配置值。
        /// </summary>
        /// <param name="key">配置键</param>
        /// <param name="value">配置值</param>
        /// <exception cref="InvalidOperationException">如果创建当前<c>AppConfig</c>
        /// 对象时<c>isReadOnly</c>为true，则会引发该异常。</exception>
        public void SetAppSetting(string key, string value)
        {
            ThrowIfReadOnly();

            KeyValueConfigurationElement setting = Configuration.AppSettings.Settings[key];
            if (setting == null)
            {
                Configuration.AppSettings.Settings.Add(key, value);
            }
            else
            {
                setting.Value = value;
            }

            OnUpdate();
        }

        /// <summary>
        /// 以对象索引方式实现的，
        /// <see cref="GetAppSetting(string)"/>和<see cref="SetAppSetting(string, string)"/>
        /// 的快捷调用方式。
        /// </summary>
        /// <param name="key">配置键</param>
        /// <returns>配置值</returns>
        /// <exception cref="System.InvalidOperationException">如果创建当前
        /// <c>AppConfig</c>对象时<c>isReadOnly</c>为true，则设置值时会引发该异常。</exception>
        public string this[string key]
        {
            get
            {
                return GetAppSetting(key);
            }

            set
            {
                SetAppSetting(key, value);
            }
        }

        /// <summary>
        /// 保存已经修改的配置
        /// </summary>
        public void Save()
        {
            Configuration.Save();
        }

        /// <summary>
        /// 更新配置时发生
        /// </summary>
        private void OnUpdate()
        {
            if (IsSaveImmediately)
            {
                Save();
            }
        }
    }
}
